package edu.gmu.streams;

import java.io.IOException;
import java.nio.ByteBuffer;

/**
 * A ByteBufferOutputStream writes primitive Java numbers
 * and characters to an output stream in a big-endian format.
 * <p>
 * This class differs from DataOutputStream in that it also
 * provides efficient methods for writing primitive arrays out.
 * <p>
 * Note: ByterBuffer "out" and the view buffers (e.g. IntBuffer)
 * keep different state - thus after writing to the IntBuffer view
 * the ByteBuffer "out"'s position has not moved, though data has 
 * certainly beed written to the array.  We adjust the position of
 * the ByteBuffer "out" appropriately after using the view buffers.
 *
 * @author  James H. Pope
 * @version $Revision:$ $Date:$
 */
public class ByteBufferOutputStream implements BlockOutputStream
{
    private static final byte ZERO  = 0x00;
    private static final byte TRUE  = 0x01;
    private static final byte FALSE = 0x00;
    
    //private int written = 0;
    private ByteBuffer out = null;
  
    
    /**
     * Creates a new ByteBufferOutputStream and chains it to the output
     * stream specified by the out argument.
     * @param  out the underlying output stream.
     */
    public ByteBufferOutputStream( ByteBuffer out )
    {
        this.out = out;
    }

    /**
     * Advances the buffers currect position by the number of bytes indicated.
     * @param num bytes to advance
     */
    public void advance( int num )
    {
        this.out.position( this.out.position() + num );
    }
    
    /**
     * Skips the number of bytes ahead as indicated.
     * @param num bytes to skip
     */
    public void skipBytes(int skip) throws IOException
    {
        // Not so efficient - but gets the job done
        for (int i = 0; i < skip; i++)
        {
            this.out.put(ZERO); // ignore return, write a zero byte
        }
    }

    /**
     * Writes byte from underlying stream.
     * @param b
     * @throws java.io.IOException
     */
    public void writeByte( byte b ) throws IOException
    {
        this.out.put(b);
    }
    
    /**
     * Writes boolean (1-byte) to underlying stream.
     * @param b
     * @throws java.io.IOException
     */
    public void writeBoolean( boolean b ) throws IOException
    {
        this.out.put( (b==true)?TRUE:FALSE );
    }
    
    /**
     * Writes short to underlying stream as two bytes.
     * @param s
     * @throws java.io.IOException
     */
    public void writeShort( short s ) throws IOException
    {
        this.out.putShort(s);
    }
    
    /**
     * Writes short to underlying stream as four bytes.
     * @param i
     * @throws java.io.IOException
     */
    public void writeInt( int i ) throws IOException
    {
        this.out.putInt(i);
    }
    
    /**
     * Writes short to underlying stream as eight bytes.
     * @param l
     * @throws java.io.IOException
     */
    public void writeLong( long l ) throws IOException
    {
        this.out.putLong(l);
    }
    
    /**
     * Writes short to underlying stream as four bytes.
     * @param f
     * @throws java.io.IOException
     */
    public void writeFloat( float f ) throws IOException
    {
        this.out.putFloat(f);
    }
    
    /**
     * Writes short to underlying stream as eight bytes.
     * @param d
     * @throws java.io.IOException
     */
    public void writeDouble( double d ) throws IOException
    {
        this.out.putDouble(d);
    }
    
    /**
     * Writes variable length (2-byte length header) of UTF characters to
     * the underlying stream. 
     * @param String
     * @throws java.io.IOException
     */
    public void writeUTF( String s ) throws IOException
    {
        byte[] bytes = s.getBytes( "UTF-8" );
        this.writeUnsignedShort(bytes.length);
        this.out.put(bytes);
    }
    
    
    /**
     * Bulk transfers bytes from given array to this buffer.
     * @param bytes
     * @throws java.io.IOException
     */
    public void write( byte[] bytes ) throws IOException
    {
        this.out.put(bytes);
    }
    
    /**
     * Bulk transfers booleans from given array to this buffer.
     * Implemenations may fall back to loop-calling writeBoolean().
     * @param booleans
     * @throws java.io.IOException
     */
    public void write( boolean[] booleans ) throws IOException
    {
        for (int i = 0; i < booleans.length; i++)
        {
            this.writeBoolean( booleans[i] );
        }
    }
    
    /**
     * Bulk transfers shorts from given array to this buffer.
     * @param shorts
     * @throws java.io.IOException
     */
    public void write( short[] shorts ) throws IOException
    {
        this.out.asShortBuffer().put(shorts);
        this.advance( shorts.length*2 );
    }
    
    /**
     * Bulk transfers integers from given array to this buffer.
     * @param integers
     * @throws java.io.IOException
     */
    public void write( int[] integers ) throws IOException
    {
        this.out.asIntBuffer().put(integers);
        this.advance( integers.length*4 );
    }
  
    /**
     * Bulk transfers floats from given array to this buffer.
     * @param floats
     * @throws java.io.IOException
     */
    public void write( float[] floats ) throws IOException
    {
        this.out.asFloatBuffer().put(floats);
        this.advance( floats.length*4 );
    }
    
    /**
     * Bulk transfers longs from given array to this buffer.
     * @param longs
     * @throws java.io.IOException
     */
    public void write( long[] longs ) throws IOException
    {
        this.out.asLongBuffer().put(longs);
        this.advance( longs.length*8 );
    }
    
    /**
     * Bulk transfers doubles from given array to this buffer.
     * @param doubles
     * @throws java.io.IOException
     */
    public void write( double[] doubles ) throws IOException
    {
        this.out.asDoubleBuffer().put(doubles);
        this.advance( doubles.length*8 );
    }
    
    /**
     * Bulk transfers strings from given array to this buffer.
     * Implemenations may fall back to loop-calling writeUTF().
     * @param strings
     * @throws java.io.IOException
     */
    public void write( String[] strings ) throws IOException
    {
        for (int i = 0; i < strings.length; i++)
        {
            this.writeUTF( strings[i] );
        }
    }
    
    //------------------------------------------------------------------------//
    // Utility method for writeUTF
    //------------------------------------------------------------------------//
    /**
     * Writes a two byte unsigned <code>short<\code> to the underlying
     * output stream in big-endian order, high byte first.
     *
     * @param    the <code>int</code> unsigned short to write.
     * @exception IOException  if the underlying stream throws an exception.
     */
    public void writeUnsignedShort( int us ) throws IOException
    {
        // Note - regardless of endian-order, spec says always big-endian - yeh!
        this.out.put((byte)(us >>> 8));
        this.out.put((byte)us );
    }
  

} //end ByteBufferOutputStream
